(function(_global) {
	var RE_SPACE = /%20/g;
	_global.onAjaxError = function(xhr) {
		var errorMessage = xhr.status + ":" + xhr.statusText;
		if (xhr.responseText) {
			errorMessage += " - " + xhr.responseText;
		}
		console.error(errorMessage);
	}
	X.Object.extend({
		params : function() {
			var params = [];
			function buildParams(name, value) {
				if (value instanceof Array) {
					X(value).each(function(val, index) {
						buildParams(name, val);
					});
				} else {
					name = String(name), value = String(value);
					params.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
				}
			}
			this.each(function(value, name) {
				if (value !== undefined && value !== null) {
					buildParams(name, value);
				}
			});
			return params.join("&").replace(RE_SPACE, "+");
		}
	});
	X.String.extend({
		url : function(opt_params) {
			var url = encodeURI(this.val());
			if (opt_params) {
				url += ("?" + X(opt_params).params());
			}
			return url;
		},
		get : function(options) {
			this.ajax("GET", options);
		},
		getSync : function(options) {
			return this.sjax("GET", options);
		},
		post : function(options) {
			this.ajax("POST", options);
		},
		postSync : function() {
			return this.sjax("GET", options);
		},
		ajax : function(method, options) {
			options = options || {};
			var url = this.val();
			var body = options.body;
			var params = options.params;
			if (!options.contentType) {
				if (params) {
					options.contentType = "application/x-www-form-urlencoded";
				} else if (body) {
					throw "When request body is raw string, you must set the contentType option";
				}
			}
			var onsuccess = options.success;
			var onerror = options.error || _global.onAjaxError;
			if (!body && params) {
				body = X(params).params();
			}
			var xhr = prepareHttpRequest(method, url, options, true);
			xhr.onreadystatechange = function() {
				if (xhr.readyState !== 4) {
					return;
				}
				if (isHttpSuccess(xhr.status)) {
					if (typeof options.success === "function") {
						options.success(xhr);
					}
				} else {
					onerror(xhr);
				}
				if (typeof options.complete === "function") {
					options.complete(xhr);
				}
			};
			if (body) {
				xhr.send(body);
			} else {
				xhr.send("");
			}
		},
		sjax : function(method, options) {
			var url = this.val();
			var xhr = prepareHttpRequest(method, url, options, false);
			xhr.send();
			if (isHttpSuccess(xhr.status)) {
				return xhr;
			} else {
				var err = new Error();
				err.name = "HttpError";
				err.request = xhr;
				throw err;
			}
		}
	});
	function prepareHttpRequest(method, url, options, async) {
		options = options || {};
		var user = options.user || undefined;
		var password = options.password || undefined;
		var headers = options.headers;
		var xhr = createHttpRequest();
		xhr.open(method, url, async, user, password);
		if (headers) {
			X(headers).each(function(value, name) {
				if (value === undefined || value === null) {
					return;
				}
				if (value instanceof Array) {
					X(value).each(function(value2) {
						xhr.setRequestHeader(String(name), String(value2));
					});
				} else {
					xhr.setRequestHeader(String(name), String(value));
				}
			});
		}
		if (options.contentType) {
			xhr.setRequestHeader("Content-Type", options.contentType);
		}
		return xhr;
	}
	function createHttpRequest() {
		try {
			return new XMLHttpRequest();
		} catch (e) {
			try {
				return new ActiveXObject("Msxml2.XMLHTTP");
			} catch (e) {
				try {
					return new ActiveXObject("Microsoft.XMLHTTP");
				} catch (e) {
					throw e;
				}
			}
		}
	}
	;
	function isHttpSuccess(status) {
		if (status >= 200 && status < 300) {
			return true;
		}
		return false;
	}
})(this);
